#ifndef UHM_DISCRETE_BASE_CACHE_HXX_
#define UHM_DISCRETE_BASE_CACHE_HXX_

// direct user interface
// - provide whether it already has matrix.  
// - do not sort
// - mesh interface to dump matrices.
// - 
namespace uhm {
  

  template<class Id_Nd_, class Uhm_>
  class Cache_ : public Disp_ {
  public:

    typedef Id_Nd_ _Id_Nd_;
    typedef Uhm_ _Uhm_;

    enum { ENTRY_NOT_VISITED = 0,
           ENTRY_PUSHED = 1,
           ENTRY_PULLED = 10 };

    class Entry_ : public Disp_ {
    private:
      Int_ _visit, _pid;
      Map_< Id_Nd_ > _factor, _schur;
      Uhm_ _uhm;

    public:
      Entry_() 
        : _visit(ENTRY_NOT_VISITED), _pid(0),
          _uhm() { } 

      Entry_(Map_<Id_Nd_> &factor, 
             Map_<Id_Nd_> &schur) 
        : _visit(ENTRY_NOT_VISITED), _pid(0),
          _factor(factor), _schur(schur),
          _uhm() {  }

      Entry_(Map_<Id_Nd_> &factor, 
             Map_<Id_Nd_> &schur, 
             Uhm_ &uhm) 
        : _visit(ENTRY_NOT_VISITED), _pid(0),
          _factor(factor), _schur(schur),
          _uhm(uhm) {  }

      Entry_(const Entry_ &b) 
        : _visit(b._visit), _pid(0),
          _factor(b._factor), _schur(b._schur), 
          _uhm(b._uhm) {  }

      virtual ~Entry_() { }

      Uhm_& uhm() { return _uhm; }

      void set_visit(Int_ visit) { _visit = visit; }
      Bool_ is_visited() { return _visit; }

      void set_pid(Int_ pid) { _pid = pid; }
      Int_ get_pid() const { return _pid; }

      Map_<Id_Nd_>& factor() { return _factor; }
      Map_<Id_Nd_>& schur() { return _schur; }

      virtual std::ostream& content(std::ostream& os) const {
        os << "- Cache Entry ";
        if (_visit) 
          os << "  Is Visited -";
        else
          os << "  Is NOT Visited -";
        os << std::endl;

        os << _factor << std::endl << _schur << std::endl << _uhm << std::endl;
        return (os);
      }

      inline bool operator==(const Entry_ &b) const {
        return (_factor == b._factor && _schur == b._schur);
      }
   
      inline bool operator!=(const Entry_ &b) const {
        return !((*this) == b);
      }

      inline bool operator<(const Entry_ &b) const {
        if (_factor != b._factor) 
          return (_factor < b._factor);

        if (_schur != b._schur) 
          return (_schur < b._schur);
        
        return false;
      }
    };

  private:
    std::set< Entry_ > _bin;
  public:

    Cache_() { }
    virtual ~Cache_() { }

    Int_ get_size() const { return _bin.size(); }

    void dump(std::vector<Entry_*> &bin, Int_ pid=0) {
      bin.clear();
      bin.reserve(this->_bin.size());
      typename std::set< Entry_ >::iterator it;
      for (it=_bin.begin();it!=_bin.end();++it) {
        Entry_* e = (Entry_*)&(*it);
        if (e->get_pid() == pid)
          bin.push_back(e);
      }
    }

    void push_begin() {
      return;
    }
    void push_end() { 
      // temporary storage
      std::vector< typename std::set<Entry_>::iterator > tmp;
      tmp.reserve(_bin.size());

      // find out not visited
      typename std::set< Entry_ >::iterator it;
      for (it=_bin.begin();it!=_bin.end();++it) {
        Entry_* e = (Entry_*)&(*it);
        if (!e->is_visited()) 
          tmp.push_back(it);
      }

      // erase all object not visited
      for (Int_ i=0;i<tmp.size();++i) 
        _bin.erase(tmp[i]);
      tmp.clear();

      // set not visited
      for (it=_bin.begin();it!=_bin.end();++it) {
        Entry_* e = (Entry_*)&(*it);
        e->set_visit(ENTRY_NOT_VISITED);
      }
    }

    Int_ push(Entry_ &in, Entry_ **out) {
      std::pair<typename std::set< Entry_ >::iterator,bool> ret;
#pragma omp critical
      {
        ret = _bin.insert(in);
        UHM_ERROR(ret.second, UHM_ERROR_CACHE_PUSH);
        *out = (Entry_*)&(*ret.first);   
        (*out)->set_visit(ENTRY_PUSHED);   
      }
      
      return (ret.second);
    }

    Int_ pull(Entry_ &in, Entry_ **out) {
      typename std::set< Entry_ >::iterator it;
      it = _bin.find(in);
      if (it == _bin.end()) 
        return false;

      *out = (Entry_*)&(*it);
      (*out)->set_visit(ENTRY_PULLED);
      
      return true;
    }

    void clear() {
      _bin.clear(); 
    }

    // Not involve with pid
    void set_matrices(Int_ type, Int_ rhs, Int_ blk) {
      typename std::set< Entry_ >::iterator it;
      for (it=_bin.begin();it!=_bin.end();++it) {
        Entry_* e = (Entry_*)&(*it);
        e->uhm().set(type, e->factor().dof(), e->schur().dof(), rhs, blk);
      }
    }

    void create_matrices() {
      typename std::set< Entry_ >::iterator it;
      for (it=_bin.begin();it!=_bin.end();++it) {
        Entry_* e = (Entry_*)&(*it);
        e->uhm().create_flat_without_buffer();
        e->uhm().create_flat_buffer(true);
      } 
    }

    void free_matrices() {
      typename std::set< Entry_ >::iterator it;
      for (it=_bin.begin();it!=_bin.end();++it) {
        Entry_* e = (Entry_*)&(*it);
        e->uhm().free_all();
      }
    }

    void random_matrices(Int_ is_spd=0) {
      typename std::set< Entry_ >::iterator it;
      if (is_spd) 
        for (it=_bin.begin();it!=_bin.end();++it) {
          Entry_* e = (Entry_*)&(*it);
          random_spd_matrix(e->uhm());
        }
      else 
        for (it=_bin.begin();it!=_bin.end();++it) {
          Entry_* e = (Entry_*)&(*it);
          random_matrix(e->uhm());
        }
    }

    Double_ get_allocated_storage() const {
      Double_ r_val=0.0;
      typename std::set< Entry_ >::iterator it;
      for (it=_bin.begin();it!=_bin.end();++it) {
        Entry_* e = (Entry_*)&(*it);
        r_val += e->uhm().get_allocated_storage();
      }
      return r_val;
    }

    virtual std::ostream& content(std::ostream& os) const  {
      this->Disp_::content(os);
      os << "- Cache_ - \n"
         << "bin size " << _bin.size() << std::endl;
      {
        typename std::set< Entry_ >::const_iterator it;
        for (it=_bin.begin();it!=_bin.end();++it)
          os << (*it) << std::endl;

        os << std::endl;
      }
      return os;
    }
  };
}


#endif
